package cn.birdbaby.plugins;

import cn.birdbaby.plugins.annotator.AbstractAnnotator;
import cn.birdbaby.plugins.annotator.AnnotatorRegistry;
import cn.birdbaby.plugins.annotator.Context;
import cn.birdbaby.plugins.annotator.classannotator.gen.genannotator.AutoGenAnnotator;
import cn.birdbaby.plugins.selector.PsiElementSelector;
import cn.birdbaby.plugins.selector.SelectType;
import cn.birdbaby.plugins.selector.SelectorRegistry;
import cn.birdbaby.plugins.common.CommonUtils;
import cn.birdbaby.plugins.sync.AbstractSyncer;
import cn.birdbaby.plugins.sync.SyncRegistry;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.module.ModuleUtil;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Computable;
import com.intellij.psi.*;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.IncorrectOperationException;

import java.util.*;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @author llorando 8/8/24 12:49 pm
 * 在进行批次处理的过程中，很多逻辑是可以复用的——和翻译api交互，删除元素时遍历psiElement树，导入元素时遍历import列表
 * 1. 预处理。把重复逻辑从业务逻辑中剥离出来，处理结果填充到上下文对象中。业务逻辑根据上下文中的信息进行判断，只关系更细粒度的业务操作即可
 * 2. 容器/池化管理/内存队列解耦。针对api、连接池等建立/释放都需要耗费大量资源的操作，建立完成后就不轻易释放，作为常驻组件接受外部请求
 */
public class Executor {

    public static void importFix(Context context) {

        // 执行写操作来添加缺失的导入
        Collection<PsiJavaCodeReferenceElement> references = PsiTreeUtil.findChildrenOfAnyType(context.getPsiJavaFile(), PsiJavaCodeReferenceElement.class);

        PsiImportList importList = (context.getPsiJavaFile()).getImportList();
        for (PsiReference reference : references) {
            PsiJavaCodeReferenceElement refElement = (PsiJavaCodeReferenceElement) reference;
            if (refElement.resolve() == null) {
                String qName = refElement.getQualifiedName();
                AbstractAnnotator annotator =  AnnotatorRegistry.find(qName);
                if (annotator != null) {
                    //获取注解全路径名；GlobalSearchScope在只有类名的时候，只在java文件中查找，不会去jar里面找
                    String fullQName = annotator.getFullName();
                    PsiClass aClass = JavaPsiFacade.getInstance(context.getProject()).findClass(fullQName, GlobalSearchScope.allScope(context.getProject()));
                    PsiImportStatementBase importStatement = JavaPsiFacade.getElementFactory(context.getProject()).createImportStatement(aClass);
                    importList.add(importStatement);

                }
            }
        }
    }
    public static <T> void excuteSync(Project project, PsiElement source, String selectorName,List<String> syncName,SelectType type) {
//        PsiDocumentManager.getInstance(project).commitAllDocuments();
        PsiElementSelector selector = PsiElementSelector.getSelector(type,selectorName);
        Map<PsiJavaFile, List<PsiElement>> psiElements = selector.selectPsiElement(source);
        List<AbstractSyncer> syncers = syncName.stream().map(SyncRegistry::find).collect(Collectors.toList());

        CommonUtils.processWithGroupId(project, UUID.randomUUID().toString(), () -> {
            psiElements.forEach((k, v) -> {
                Context c=new Context(k);

                for (PsiElement element : v) {
                    for (AbstractSyncer syncer : syncers) {
                        syncer.sync((PsiClass) element);
                    }
                }
                importFix(c);
            });

        });
    }
    public static <T> void excuteSync(Project project, PsiElement source, String selectorName,String syncName,SelectType type) {
//        PsiDocumentManager.getInstance(project).commitAllDocuments();
        PsiElementSelector selector = PsiElementSelector.getSelector(type,selectorName);
        Map<PsiJavaFile, List<PsiElement>> psiElements = selector.selectPsiElement(source);
        List<AbstractSyncer> syncers = Arrays.asList(SyncRegistry.find(syncName));

        CommonUtils.processWithGroupId(project, UUID.randomUUID().toString(), () -> {
            psiElements.forEach((k, v) -> {
                Context c=new Context(k);

                for (PsiElement element : v) {
                    for (AbstractSyncer syncer : syncers) {
                        syncer.sync((PsiClass) element);
                    }
                }
                importFix(c);
            });

        });
    }
    public static <T> void excuteAnnotate(Project project, PsiElement source, String selectorName, String annotationName, SelectType type) {
//        PsiDocumentManager.getInstance(project).commitAllDocuments();
        PsiElementSelector selector = PsiElementSelector.getSelector(type,selectorName);
        Map<PsiJavaFile, List<PsiElement>> psiElements = selector.selectPsiElement(source);
        List<AbstractAnnotator> annotators = Arrays.asList(AnnotatorRegistry.find(annotationName));

        CommonUtils.processWithGroupId(project, UUID.randomUUID().toString(), () -> {
            psiElements.forEach((k, v) -> {
                Context c=new Context(k);

                for (PsiElement element : v) {
                    for (AbstractAnnotator annotator : annotators) {
                        if (annotator.support(element)) {
                            c.setCurrentElement(element);
                            annotator.buildPsiAnnotation(c);
                        }
                    }
                }
                importFix(c);
                importClassIfNotImported(c,project,c.getParentPackageName() + ".infrastruction.constant.Constants");
            });

        });
    }

    public static <T> void excuteAnnotate(Project project, Map<PsiJavaFile, List<PsiElement>> psiElements, List<AbstractAnnotator> annotators, Consumer<Context> additionalOperation) {
        CommonUtils.processWithGroupId(project, UUID.randomUUID().toString(), () -> {
            psiElements.forEach((k, v) -> {
                Context c=new Context(k);

                for (PsiElement element : v) {
                    for (AbstractAnnotator annotator : annotators) {
                        if (annotator.support(element)) {
                            c.setCurrentElement(element);
                            annotator.buildPsiAnnotation(c);
                        }
                    }
                }
                importFix(c);
                additionalOperation.accept(new Context(k));
            });

        });
    }


    public static void importClassIfNotImported(Context context,final Project project, final String className) {
        WriteCommandAction.runWriteCommandAction(project, (Computable<Object>) () -> {
            PsiImportList importList = context.getPsiJavaFile().getImportList();
            if (importList != null) {
                // Check if the class is already imported
                PsiClass aClass = JavaPsiFacade.getInstance(project).findClass(className, GlobalSearchScope.moduleScope(ModuleUtil.findModuleForFile(context.getPsiJavaFile())));
                if (aClass != null) {
                    if (!isAlreadyImported(importList, aClass)) {
                        try {
                            PsiImportStatementBase importStatement = JavaPsiFacade.getElementFactory(project).createImportStatement(aClass);
                            importList.add(importStatement);
                        } catch (IncorrectOperationException e) {
                            // Handle exception
                            e.printStackTrace();
                        }
                    }
                } else {
                    // Class not found, handle this case as needed.
                    System.out.println("Class not found: " + className);
                }
            }
            return true;
        });
    }

    private static boolean isAlreadyImported(PsiImportList importList, PsiClass aClass) {
        PsiImportStatementBase[] existingImports = importList.getAllImportStatements();
        String qualifiedName = aClass.getQualifiedName();
        for (PsiImportStatementBase importStatement : existingImports) {
            PsiJavaCodeReferenceElement importReference = importStatement.getImportReference();
            if (importReference != null && importReference.getCanonicalText().equals(qualifiedName)) {
                return true;
            }
        }
        return false;
    }


}
